/*
 * Copyright 2002-2005 Robert Breidecker.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sourceforge.jsorter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import net.sourceforge.jsorter.SortReflect.MethodException;

/**
 * Used for sorting lists of objects. This class is particularly good for
 * sorting table data and multi-column lists.
 * 
 * Note: This is class is not thread safe.
 * 
 * @author Robert Breidecker
 */
public class Sorter<T> {
	/**
	 * Holds the data used for sorting.
	 */
	private List table = null;

	/**
	 * The columns to sort by.
	 */
	private List sortColumns = null;

	/**
	 * Indicate how to treat null data values.
	 */
	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
  
  public List<T> getReflectResults() {
    List<T> results = new ArrayList<T>(table.size());
    Iterator<T> iterator = table.iterator();
    while (iterator.hasNext()) {
      List row = (List)iterator.next();
      SortHolder<T> sortHolder = (SortHolder<T>)row.get(0);
      results.add(sortHolder.object);
    }
    return results;
  }
  
  public static Sorter createReflect(ReflectColumns reflectColumns, List objects, int nullBehavior) throws MethodException {
    SortReflect[] sortReflects = reflectColumns.getSortReflects();
    List table = SortHolder.getTable(sortReflects, objects, nullBehavior);
    Sorter sorter = new Sorter(table, reflectColumns.getColumns());
    sorter.setNullBehavior(nullBehavior);
    return sorter;
  }
  
	/**
	 * Sorter constructor. This version of the constructor does not use any
	 * parameters. If you use this constructor, you should use the setter
	 * methods to set the values you want the sort routine to use.
	 */
	public Sorter() {
		super();
	}

	/**
	 * Sorter constructor. This version of the contructor takes only a table as
	 * a parameter. Since no sort columns are specified, this will cause the
	 * sort routine to sort the table using all of the columns in the table in
	 * ascending order. To change the table or sort columns, you must use the
	 * appropriate "set" methods.
	 * 
	 * @param table
	 *            This field should be an object that implements the List
	 *            interface. This list we will call the "table". Classes that
	 *            implement the List interface include ArrayList, Vector and
	 *            LinkedList. Each item in the table list should itself be an
	 *            object that implements the List interface. These lists we will
	 *            call "columns". Column lists should only contain objects that
	 *            implement the Comparable interface. Classes that implement the
	 *            Comparable interface include String, Integer, Long, Short,
	 *            Float, Byte, Double and Date. See the standard JDK documention
	 *            for Comparator for a complete list. The class type for each
	 *            object in a column list must be consistent or a
	 *            ClassCaseException will be thrown during the sort.
	 */
	public Sorter(final List table) {
		// Set the values for the new sorter object.
		setTable(table);
	}

	/**
	 * Sorter constructor. This version of the contructor takes a table and a
	 * column order as parameters. A sort column for each column in the data
	 * will be created with the column order specified. This will cause the sort
	 * routine to sort the table using all of the columns in the table (position
	 * 0 and up or left to right) in the order specified in the column order. To
	 * override the table or sort columns, you must use the appropriate "set"
	 * methods. A vector will be used for storing the sort columns that are
	 * dynamically created.
	 * 
	 * @param table
	 *            This field should be an object that implements the List
	 *            interface. This list we will call the "table". Classes that
	 *            implement the List interface include ArrayList, Vector and
	 *            LinkedList. Each item in the table list should itself be an
	 *            object that implements the List interface. These lists we will
	 *            call "columns". Column lists should only contain objects that
	 *            implement the Comparable interface. Classes that implement the
	 *            Comparable interface include String, Integer, Long, Short,
	 *            Float, Byte, Double and Date. See the standard JDK documention
	 *            for Comparator for a complete list. The class type for each
	 *            object in a column list must be consistent or a
	 *            ClassCaseException will be thrown during the sort.
	 * 
	 * @param columnOrder
	 *            This value will be used for specifying the order in which the
	 *            columns in the table are sorted. Sort order can either be
	 *            ascending or descending. The ASCENDING_ORDER and
	 *            DESCENDING_ORDER constants in this class should be used for
	 *            this value.
	 */
	public Sorter(final List table, final int columnOrder) {
		// Get the first row.
		List firstRow = null;
		if (table.size() > 0) {
			firstRow = (List)table.get(0);
		}

		// Build the sort columns.
		List sortColumns = null;
		if (firstRow != null) {
			sortColumns = new Vector();
			final int numColumns = firstRow.size();
			for (int columnCtr = 0; columnCtr < numColumns; columnCtr++) {
				final SortableColumn sortColumn = new SortableColumnImpl(columnCtr, columnOrder);

				sortColumns.add(sortColumn);
			}
		}

		// Set the values for the new sorter object.
		setTable(table);
		setSortColumns(sortColumns);
	}

	/**
	 * Sorter constructor. This version of the contructor takes a table and a
	 * list of sort columns as parameters. To override the table or sort
	 * columns, you must use the appropriate "set" methods.
	 * 
	 * @param table
	 *            This field should be an object that implements the List
	 *            interface. This list we will call the "table". Classes that
	 *            implement the List interface include ArrayList, Vector and
	 *            LinkedList. Each item in the table list should itself be an
	 *            object that implements the List interface. These lists we will
	 *            call "columns". Column lists should only contain objects that
	 *            implement the Comparable interface. Classes that implement the
	 *            Comparable interface include String, Integer, Long, Short,
	 *            Float, Byte, Double and Date. See the standard JDK documention
	 *            for Comparator for a complete list. The class type for each
	 *            object in a column list must be consistent or a
	 *            ClassCaseException will be thrown during the sort.
	 * 
	 * @param sortColumns
	 *            Sort columns are a list of numbers specifying the colums to
	 *            sort the table by. Each number in the list should be an
	 *            instance of the SortColumn class represents a position of a
	 *            column in the table. Column position start at zero just as
	 *            they do in the standard Collection classes and Java arrays.
	 *            When this field is null, there must be only one and only one
	 *            column order specified. The sort routine will then sort using
	 *            every column in the table starting from position 0 and up or
	 *            left to right in ascending order.
	 */
	public Sorter(final List table, final List sortColumns) {
		// Set the values for the new sorter object.
		setTable(table);
		setSortColumns(sortColumns);
	}

	/**
	 * Returns the list of columns to sort by.
	 * 
	 * @return The list of columns to sort by.
	 */
	public List getSortColumns() {
		return sortColumns;
	}

	/**
	 * Returns the table of sort data.
	 * 
	 * @return The table of sort data.
	 */
	public List<T> getTable() {
		return table;
	}

	/**
	 * Updates the list of sort columns.
	 * 
	 * @param sortColumns
	 *            Sort columns are a list of numbers specifying the colums to
	 *            sort the table by. Each number in the list should be an
	 *            instance of the SortColumn class represents a position of a
	 *            column in the table. Column position start at zero just as
	 *            they do in the standard Collection classes and Java arrays.
	 *            When this field is null, there must be only one and only one
	 *            column order specified. The sort routine will then sort using
	 *            every column in the table starting from position 0 and up or
	 *            left to right in ascending order.
	 */
	public void setSortColumns(final List sortColumns) {
		validateSortColumns(sortColumns);
		this.sortColumns = sortColumns;
	}

	/**
	 * Updates the table of sort data.
	 * 
	 * @param table
	 *            This field should be an object that implements the List
	 *            interface. This list we will call the "table". Classes that
	 *            implement the List interface include ArrayList, Vector and
	 *            LinkedList. Each item in the table list should itself be an
	 *            object that implements the List interface. These lists we will
	 *            call "columns". Column lists should only contain objects that
	 *            implement the Comparable interface. Classes that implement the
	 *            Comparable interface include String, Integer, Long, Short,
	 *            Float, Byte, Double and Date. See the standard JDK documention
	 *            for Comparator for a complete list. The class type for each
	 *            object in a column list must be consistent or a
	 *            ClassCaseException will be thrown during the sort.
	 */
	public void setTable(final List table) {
		validateTable(table);
		this.table = table;
	}

	/**
	 * Returns the null behavior for this object.
	 * 
	 * @return An integer representing the constant that indicates how null data
	 *         values should behave while being sorted. See the null behavior
	 *         constants in this class. The default value for this class is
	 *         NULLS_ARE_INVALID.
	 */
	public int getNullBehavior() {
		return nullBehavior;
	}

	/**
	 * Set the null behavior for this object.
	 * 
	 * @param nullBehavior
	 *            An integer representing the constant that indicates how null
	 *            data values should behave while being sorted. See the null
	 *            behavior constants in this class.
	 * 
	 * @throws IllegalArgumentException
	 *             Thrown if the null behavior value is not valid.
	 */
	public void setNullBehavior(final int nullBehavior) {
		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
			throw new IllegalArgumentException("Invalid null behavior.");
		}

		this.nullBehavior = nullBehavior;
	}

	/**
	 * This routine sorts the table of data. The sort uses the sort columns to
	 * determine how to sort the data.
	 * 
	 * @exception IllegalStateException
	 *                The data in this class is in an invalid state.
	 */
	public void sort() {
		// Sort the data.
    List sortColumns = getSortColumns();
    //System.out.println("sortColumns: "+sortColumns);
		Collections.sort(table, new SortComparator(getSortColumns(), getNullBehavior()));
	}
  
  public List<T> sortReflect() {
    sort();
    return getReflectResults();
  }
  
	/**
	 * This routine sorts the table of data using a comparator provided in the
	 * parameters to do the sorting. The sort columns for this class will not be
	 * used unless the input comparator has been coded to do so.
	 * 
	 * @param comparator
	 *            A comparator to use for comparing the data rows in the table
	 *            that has already been set on this class.
	 * 
	 * @exception IllegalStateException
	 *                The data in Sorter is in an invalid state.
	 */
	public void sort(final Comparator comparator) {
		// Sort the data.
		Collections.sort(getTable(), comparator);
	}

	/**
	 * Validates the list of sort columns.
	 * 
	 * @param sortColumns
	 *            The list of sort columns to validate.
	 * 
	 * @exception IllegalArgumentException
	 *                Input data is invalid.
	 */
	private void validateSortColumns(final List sortColumns) {
		if (sortColumns != null) {
			// Validate size.
			if (sortColumns.size() < 1) {
				throw new IllegalArgumentException("Sort columns can "+ "not be empty.");
			}

			for (int ctr = 0, size = sortColumns.size(); ctr < size; ctr++) {
				// Validate for SortColumns.
				if (!(sortColumns.get(ctr) instanceof SortableColumn)) {
					throw new IllegalArgumentException("The list of sort "+ "columns does not contain all SortColumn objects.");
				}

				// Validate for greater than or equal to zero.
				if (((SortableColumn) sortColumns.get(ctr)).getColumnPosition() < 0) {
					throw new IllegalArgumentException("A sort column number is less than zero.");
				}

				// Validate for invalid column order.
				if (((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.ASCENDING_ORDER
						&& ((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.DESCENDING_ORDER) {
					throw new IllegalArgumentException("A sort column order is invalid.");
				}
			}
		}
	}

	/**
	 * Validates the table of sort data.
	 * 
	 * @param table
	 *            The table of sort data to validate.
	 * 
	 * @exception IllegalArgumentException
	 *                Input data is invalid.
	 */
	private void validateTable(final List table) {
		// Validate for null.
		if (table == null) {
			throw new IllegalArgumentException("The table of sort data "+ "can not be null.");
		}

		// Validate for Lists.
		if (table.size() > 0) {
			if (!(table.get(0) instanceof List)) {
				throw new IllegalArgumentException("The table does not implement " + "the List interface.");
			}
		}
	}
}